﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Interpreter.TableBuilder;

namespace Interpreter
{
	class Program
	{
		private static readonly bool CATCH_EXCEPTIONS = false;

		private static string GetComponent(char key, Dictionary<char, string> components)
		{
			if (!components.ContainsKey(key))
			{
				throw new Exception("Component is missing: " + key);
			}

			return components[key];
		}

		// Wonderful ideas:
		// - put most of this table builder logic in the compiler so that all the metadata table
		//   builders are just double splits and convert to int
		// - make the ID table follow the same format as the string table even though it'll be a
		//   few more bytes
		// - better yet, use strings directly, but only escape weird characters so they're both small
		// - create an op code to assign a value to a direct field. This will make class initializing
		//   require 2 less VM ops per field. Other this.foo = something instances can also be optimized
		//   into this new op code as well if foo is clearly a field at compile time.

		static void Main(string[] args)
		{
#if DEBUG
			string bfxPath = "C:\\TestApp\\testapp.bfx";
#else
			// This string is dynamically modified after compile to point to the bfx file.
			string bfxPath = "*****************************************************************************************************";

			bool modified = false;
			for (int i = 0; i < 100; ++i)
			{
				if (bfxPath[i] != '*')
				{
					modified = true;
					break;
				}
			}

			if (modified)
			{
				bfxPath = bfxPath.Replace("*", "");
			}
			else
			{
				throw new Exception("No BFX path set");
			}
#endif

			if (!System.IO.Path.IsPathRooted(bfxPath))
			{
				string exeFile = System.Reflection.Assembly.GetEntryAssembly().Location;
				string exeLocation = System.IO.Path.GetDirectoryName(exeFile);
				bfxPath = System.IO.Path.Combine(exeLocation, bfxPath);
			}

			string bfxContents = System.IO.File.ReadAllText(bfxPath);

			Dictionary<char, string> components = GetComponents(bfxContents);

			Metadata data = new Metadata();

			data.ProgramRoot = System.IO.Path.GetDirectoryName(bfxPath);

			data.IdTable = IdentifierTableBuilder.GetTable(GetComponent('d', components));
			data.StringTable = StringTableBuilder.GetTable(GetComponent('t', components));
			data.IntegerTable = IntegerTableBuilder.GetTable(GetComponent('i', components));
			data.FloatTable = FloatTableBuilder.GetTable(GetComponent('a', components));

			data.IdReverseTable = ReverseTable(data.IdTable);

			data.Namespaces = GenericTableBuilder.GetTable(GetComponent('n', components));

			data.StaticMethods = GetMethodSignatures(GenericTableBuilder.GetTable(GetComponent('M', components)));
			data.Methods = GetMethodSignatures(GenericTableBuilder.GetTable(GetComponent('m', components)));

			data.StaticPropertyGetters = GenericTableBuilder.GetTable(GetComponent('G', components));
			data.StaticPropertySetters = GenericTableBuilder.GetTable(GetComponent('S', components));
			data.PropertyGetters = GenericTableBuilder.GetTable(GetComponent('g', components));
			data.PropertySetters = GenericTableBuilder.GetTable(GetComponent('s', components));

			data.Constructors = GenericTableBuilder.GetTable(GetComponent('x', components));
			data.StaticConstructors = GenericTableBuilder.GetTable(GetComponent('X', components));

			data.Fields = GenericTableBuilder.GetTable(GetComponent('f', components));
			data.StaticFields = GenericTableBuilder.GetTable(GetComponent('F', components));

			data.StartingPosition = Util.ConvertFromHex(GetComponent('A', components));

			data.DefaultParameters = GenericTableBuilder.GetTable(GetComponent('R', components));

			int[][] classInfo = GenericTableBuilder.GetTable(GetComponent('C', components));
			int[][] classMembers = GenericTableBuilder.GetTable(GetComponent('c', components));
			int[][] classBaseClass = GenericTableBuilder.GetTable(GetComponent('H', components));

			data.StaticClassMemberLookupGet = GetClassMemberLookup(classMembers, true, true);
			data.StaticClassMemberLookupSet = GetClassMemberLookup(classMembers, true, false);
			data.ClassMemberLookupGet = GetClassMemberLookup(classMembers, false, true);
			data.ClassMemberLookupSet = GetClassMemberLookup(classMembers, false, false);

			data.Classes = GetClasses(classInfo);
			data.ClassesFast = classInfo;

			// instruction builder should also convert switches into lookup tables and return that via a dictionary property
			InstructionBuilder instrBuilder = new InstructionBuilder(GetComponent('O', components));

			data.OpCodes = instrBuilder.OpCodes;
			data.OpFirstParam = instrBuilder.FirstParam;
			data.OpMultiParam = instrBuilder.MultiParam;

			OperatorTableBuilder opTable = new OperatorTableBuilder();
			data.OpCodeMap = opTable.OpMap;
			data.ByteCodeToOpEnum = opTable.ByteCodeToOp;

			data.PrimitiveMethodLookup = Util.SplitToInt(GetComponent('h', components), '`');
			data.NativeFunctions = BuiltIns.FunctionRegistry.CreateNewRegistry().GenerateLookup();

			int[][] specialData = GenericTableBuilder.GetTable(GetComponent('r', components));
			data.SystemExceptionClassId = specialData[1][0];
			data.RuntimeExceptionInvoker = specialData[2][0];
			data.ClassAncestry = BuildClassAncestryTable(classInfo);

			data.RawSource = BuildSourceLookup(GetComponent('o', components));
			data.TokenInfo = BuildTokenTable(data, GetComponent('T', components));
			data.StackInfo = BuildStringTable(GetComponent('k', components));

			if (CATCH_EXCEPTIONS)
			{
				try
				{
					Interpreter.Interpret(data, null);
				}
				catch (ThisShouldNeverHappenException evilException)
				{
					System.Console.WriteLine("The following exception occurred but should have never ever happened in the first place:\n" + evilException.Message);
					System.Console.Read();
				}
				catch (NotImplementedException notImplementedException)
				{
					System.Console.WriteLine("The following functionality is not implemented yet:" + notImplementedException.Message);
					System.Console.Read();
				}
			}
			else
			{
				Interpreter.Interpret(data, null);
			}
		}

		private const int BASE_CLASS_ID_INDEX = 7;

		// This table is a lookup of class ID --> int[]
		// each int[] for the class is a list of parents with the root parent at the end.
		private static int[][] BuildClassAncestryTable(int[][] classes)
		{
			List<int[]> table = new List<int[]>() { new int[0] };

			List<int> row = new List<int>();
			for (int i = 1; i < classes.Length; ++i)
			{
				int parentId = classes[i][BASE_CLASS_ID_INDEX];
				row.Clear();
				while (parentId != 0)
				{
					row.Add(parentId);
					parentId = classes[parentId][BASE_CLASS_ID_INDEX];
				}
				table.Add(row.ToArray());
			}

			return table.ToArray();
		}

		private static FileInfo[] BuildSourceLookup(string rawFileInfo)
		{
			List<FileInfo> files = new List<FileInfo>() { null };
			for (int i = 0; i < rawFileInfo.Length; ++i)
			{
				string fileInfoSizeHexString = rawFileInfo.Substring(i, 8);
				i += 8;
				int fileInfoSize = Util.ConvertFromHex(fileInfoSizeHexString);
				string fileInfoRawString = rawFileInfo.Substring(i, fileInfoSize);
				i += fileInfoSize;
				--i;

				files.Add(FileInfo.ParseFromRawData(Util.ConvertFromHexString(fileInfoRawString)));
			}

			return files.ToArray();
		}

		private static string[] BuildStringTable(string rawTable)
		{
			string[] rawEntries = rawTable.Split('`');
			List<string> entries = new List<string>();
			List<string> entry = new List<string>();
			char c;
			string code;
			foreach (string rawEntry in rawEntries)
			{
				for (int i = 0; i < rawEntry.Length; ++i)
				{
					c = rawEntry[i];
					if (c == '#')
					{
						code = "" + rawEntry[i + 1] + rawEntry[i + 2];
						c = (char)(byte)Util.ConvertFromHex(code);
						i += 2;
					}

					entry.Add("" + c);
				}

				entries.Add(string.Join("", entry));
				entry.Clear();
			}

			return entries.ToArray();
		}

		private static readonly Token[] NO_TOKENS_FOR_YOU = new Token[0];

		private static Token[][] BuildTokenTable(Metadata data, string rawTokenInfo)
		{
			List<Token[]> tokenTable = new List<Token[]>();
			List<Token> tokenRow = new List<Token>();
			int[][] tokenData = TableBuilder.GenericTableBuilder.GetTableWithoutDummyFirstEntry(rawTokenInfo);
			for (int i = 0; i < tokenData.Length; ++i)
			{
				int[] row = tokenData[i];
				if (row == null || row.Length <= 1)
				{
					tokenTable.Add(NO_TOKENS_FOR_YOU);
				}
				else
				{
					for (int j = 0; j < row.Length; j += 3)
					{
						int fileIndex = row[j];
						int lineIndex = row[j + 1];
						int columnIndex = row[j + 2];

						if (lineIndex == 241)
						{

						}
						Token token = new Token(data, row[j], row[j + 1], row[j + 2]);
						tokenRow.Add(token);
					}

					tokenTable.Add(tokenRow.ToArray());
					tokenRow.Clear();
				}
			}

			return tokenTable.ToArray();
		}

		// make sure this is synchronized with its counterpart in the compiler
		private const int SIG_CONSTRUCTOR = 1;
		private const int SIG_FIELD = 2;
		private const int SIG_METHOD = 3;
		private const int SIG_PROPERTY_GET = 4;
		private const int SIG_PROPERTY_SET = 5;
		private const int SIG_STATIC_CONSTRUCTOR = 6;
		private const int SIG_STATIC_FIELD = 7;
		private const int SIG_STATIC_METHOD = 8;
		private const int SIG_STATIC_PROPERTY_GET = 9;
		private const int SIG_STATIC_PROPERTY_SET = 10;
		private const int SIG_NESTED_CLASS = 11;

		private static MethodSignature[] GetMethodSignatures(int[][] rawData)
		{
			List<MethodSignature> signatures = new List<MethodSignature>() { null };

			for (int i = 1; i < rawData.Length; ++i)
			{
				int[] row = rawData[i];
				MethodSignature s = new MethodSignature();
				s.ClassId = row[0];
				s.NameId = row[1];
				s.StartLocation = row[2];

				
				List<int> paramStepIds = new List<int>();
				List<int> paramDefaultValues = new List<int>();
				for (int j = 3; j < row.Length; j += 2)
				{
					paramStepIds.Add(row[j]);
					paramDefaultValues.Add(row[j + 1]);
				}

				s.ParamVars = paramStepIds.ToArray();
				s.ParamDefaultValues = paramDefaultValues.ToArray();

				signatures.Add(s);
			}

			return signatures.ToArray();
		}

		// class ID --> { step ID --> { type, member ID } }
		private static Dictionary<int, int[]>[] GetClassMemberLookup(int[][] classMemberLookup, bool isStatic, bool isGetContext)
		{
			List<Dictionary<int, int[]>> output = new List<Dictionary<int, int[]>>() { null };

			for (int i = 1; i < classMemberLookup.Length; ++i)
			{
				Dictionary<int, int[]> lookup = new Dictionary<int, int[]>();

				int[] row = classMemberLookup[i];

				for (int j = 0; j < row.Length; j += 3)
				{
					int stepId = row[j];
					int type = row[j + 1];
					int id = row[j + 2];
					bool memberIsStatic = type > 5;

					if (memberIsStatic != isStatic)
					{
						continue;
					}

					if (isGetContext)
					{
						if (type == 4)
						{

						}

						if (type == 5 || type == 10) // setters
						{
							continue;
						}
					}
					else
					{
						if (type == 4 || type == 9 || // getters
							type == 11 || // nested class
							type == 3 || type == 8 // methods
							)
						{
							continue;
						}
					}

					lookup.Add(row[j], new int[] { row[j + 1], row[j + 2] });
				}

				output.Add(lookup);
			}
			return output.ToArray();
		}

		private static Value[] GetClasses(int[][] classInfo)
		{
			List<Value> output = new List<Value>() { };

			for (int i = 0; i < classInfo.Length; ++i)
			{
				// type, static values, is initialized
				output.Add(new Value(Value.TYPE_CLASS, i, new Dictionary<int, Value>(), 0));
			}
			return output.ToArray();
		}

		private static Dictionary<char, string> GetComponents(string text)
		{
			Dictionary<char, string> output = new Dictionary<char,string>();
			int index = 0;
			while (index < text.Length)
			{
				string lengthString = text.Substring(index, 8);
				int length = Util.ConvertFromHex(lengthString);
				index += 8;
				char key = text[index];
				string value = text.Substring(index + 1, length - 1);
				index += length;

				output[key] = value;
			}
			return output;
		}

		private static Dictionary<string, int> ReverseTable(string[] table)
		{
			Dictionary<string, int> output = new Dictionary<string, int>();
			string value;
			for (int i = 0; i < table.Length; ++i)
			{
				value = table[i];
				if (value != null)
				{
					output[value] = i;
				}
			}
			return output;
		}
	}
}
